﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Library.Winform.Draw.Tools;
using Library.Util;
namespace Library.Winform.Draw {
	public partial class DrawContainer : UserControl, IDrawContainer {
		private bool _lockDesign;
		public bool LockDesign { get { return _lockDesign; } set { _lockDesign = value; } }
		protected IToolGraphic ActiveTool;
		public IToolGraphic ActiveToolDraw {
			get { return ActiveTool; }
			set { ActiveTool = value; }
		}


		public void SelectAll(bool isSelect) {
			GraphicsList.Select(isSelect);
		}

		public void AddObjectDraw(ObjectDraw objectDraw) {
			InsertObjectDraw(GraphicsList.Count, objectDraw);
		}

		public void InsertObjectDraw(int index, ObjectDraw objectDraw) {
			var adding = new ObjectDrawAddingEventArgs(objectDraw);
			InvokeObjectDrawAdding(adding);
			if (!adding.Cancel) {
				GraphicsList.Insert(index, objectDraw);
				InvokeObjectDrawAdded(adding);
			}
		}

		public SelectedDrawEnumerable GetSelected() {
			return GraphicsList.Selection;
		}

		public void SelectInRectangle(Rectangle rectangle) {
			GraphicsList.SelectInRectangle(rectangle);
		}

		public void Remove(ObjectDraw objectDraw) {
			Remove(objectDraw, true);
		}

		public void Clear() {
			GraphicsList.Clear();
		}

		public void Remove(ObjectDraw objectDraw, bool fireRemoving) {
			Remove(objectDraw, fireRemoving, true);
		}

		public void Remove(ObjectDraw objectDraw, bool fireRemoving, bool fireRemoved) {
			if (fireRemoving) {
				var removing = new ObjectDrawRemovingEventArgs(objectDraw);
				InvokeObjectDrawRemoving(removing);
				if (!removing.Cancel) {
					GraphicsList.Remove(objectDraw);
					if (fireRemoved)
						InvokeObjectDrawRemoved(removing);
				}
			} else {
				GraphicsList.Remove(objectDraw);
				if (fireRemoved)
					InvokeObjectDrawRemoved(new ObjectDrawRemovedEventArgs(objectDraw));
			}



		}

		public void ClearObjectDraw() {
			while (GraphicsList.Count > 0) {
				Remove(GraphicsList[0]);
			}
		}



		public ObjectDraw FindDraw(ScrollMouseEventArgs e) {
			return GraphicsList.FindDraw(e);
		}

		public ObjectDraw FindDraw(Point pointWithScrollOffset) {
			return GraphicsList.FindDraw(pointWithScrollOffset);
		}

		private readonly GraphicsCollection _graphics;

		public void Update(int x, int y, int width, int height) {
			UpdateBounds(x, y, width, height);
		}

		public GraphicsCollection GraphicsList { get { return _graphics; } }

		public Point ScrollOffset {
			get { return new Point(-AutoScrollPosition.X, -AutoScrollPosition.Y); }
		}

		public DrawContainer() {
			InitializeComponent();
			_graphics = new GraphicsCollection(this);
			Init();
		}

		private void Init() {
			_pixel = new Panel();
			SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
			_pixel.Size = new Size(1, 1);
			_pixel.BackColor = Color.Transparent;
			Controls.Add(_pixel);

			AutoScroll = true;
		}

		public override void Refresh() {
			int y;
			int x = y = 0;
			for (int i = 0; i < GraphicsList.Count; i++) {
				var graphics = GraphicsList[i];
				var max = graphics.MaxPosition;
				if (max.X > x) x = max.X;
				if (max.Y > y) y = max.Y;
			}
			_pixel.Location = new Point(x - ScrollOffset.X, y - ScrollOffset.Y);
			base.Refresh();
		}

		private Panel _pixel;
		private bool _isOnPaint;
		protected override void OnPaint(PaintEventArgs e) {
			if (_isOnPaint) return;
			_isOnPaint = true;
			base.OnPaint(e);
			try {
				GraphicsList.Draw(e.Graphics);
			} catch (Exception exception) {
				InvokePaintingException(new PaintingExceptionEventArgs(exception));
				throw;
			} finally {
				_isOnPaint = false;
			}
		}

		protected override void OnMouseDown(MouseEventArgs e) {
			base.OnMouseDown(e);
			if (LockDesign) return;
			if (ActiveTool == null) return;
			if (e.Button == MouseButtons.Left) {
				ActiveTool.OnMouseDown(this, e);
			} else if (e.Button == MouseButtons.Right) {
				var item = _graphics.FindDraw(new Point(e.X + ScrollOffset.X, e.Y + ScrollOffset.Y));
				if (item == null) {
					for (int i = 0; i < _graphics.Count; i++) {
						_graphics[i].Selected = false;
					}
				} else {
					if (!item.Selected)
						_graphics.SelectInRectangle(new Rectangle(e.X + ScrollOffset.X, e.Y + ScrollOffset.Y, 1, 1));
				}
			}
		}

		protected override void OnMouseUp(MouseEventArgs e) {
			base.OnMouseUp(e);
			if (LockDesign) return;
			if (ActiveTool == null) return;
			if (e.Button == MouseButtons.Left) {
				ActiveTool.OnMouseUp(this, e);
				ActiveTool = ToolPointer.Pointer;
			} else {

				for (int i = 0; i < _graphics.Count; i++) {
					var gr = _graphics[i];
					if (gr.Selected && !gr.HasGroup) {
						if (gr.ContextMenuStrip != null) {
							gr.ContextMenuStrip.Show(PointToScreen(e.Location));
							Refresh();
							return;
						}
					}
				}
			}
		}

		protected override void OnMouseMove(MouseEventArgs e) {
			base.OnMouseMove(e);
			if (LockDesign) return;
			if (ActiveTool == null) return;
			if (e.Button == MouseButtons.Left || e.Button == MouseButtons.None) {
				ActiveTool.OnMouseMove(this, e);
			}
		}

		protected override void OnMouseClick(MouseEventArgs e) {
			if (LockDesign) return;
			ToolPointer.Pointer.OnMouseClick(this, e);
		}

		protected override void OnDoubleClick(EventArgs e) {
			if (LockDesign) return;
			ToolPointer.Pointer.OnDoubleClick(this, e);
		}

		protected override void OnMouseDoubleClick(MouseEventArgs e) {
			if (LockDesign) return;
			ToolPointer.Pointer.OnMouseDoubleClick(this, e);
		}

		protected override bool ProcessDialogKey(Keys keyData) {
			//process press Ctr + A
			if (LockDesign) return base.ProcessDialogKey(keyData);
			const Keys controlA = Keys.Control | Keys.A;
			if (keyData == controlA) {
				if (GraphicsList.Count > 0) {
					SelectAll(true);
					Refresh();
				}
				return true;
			}

			if ((keyData & (Keys.Alt | Keys.Control)) == Keys.None) {
				Keys keyCode = keyData & Keys.KeyCode;
				if (keyCode == Keys.Delete) {
					var list = new List<ObjectDraw>();
					for (int i = 0; i < GraphicsList.Count; i++) {
						var g = GraphicsList[i];
						if (g.Selected) {
							list.Add(g);
						}
					}

					foreach (var objectDraw in list) {
						Remove(objectDraw);
					}

					if (list.Count > 0) Refresh();
				}
				return true;
			}
			return base.ProcessDialogKey(keyData);
		}

		public event EventHandler<ObjectDrawAddedEventArgs> ObjectDrawAdded;

		private void InvokeObjectDrawAdded(ObjectDrawAddedEventArgs e) {
			EventHandler<ObjectDrawAddedEventArgs> handler = ObjectDrawAdded;
			if (handler != null) handler(this, e);
		}

		public event EventHandler<ObjectDrawAddingEventArgs> ObjectDrawAdding;

		private void InvokeObjectDrawAdding(ObjectDrawAddingEventArgs e) {
			EventHandler<ObjectDrawAddingEventArgs> handler = ObjectDrawAdding;
			if (handler != null) handler(this, e);
		}

		public event EventHandler<ObjectDrawRemovingEventArgs> ObjectDrawRemoving;

		private void InvokeObjectDrawRemoving(ObjectDrawRemovingEventArgs e) {
			EventHandler<ObjectDrawRemovingEventArgs> handler = ObjectDrawRemoving;
			if (handler != null) handler(this, e);
		}

		public event EventHandler<ObjectDrawRemovedEventArgs> ObjectDrawRemoved;

		private void InvokeObjectDrawRemoved(ObjectDrawRemovedEventArgs e) {
			EventHandler<ObjectDrawRemovedEventArgs> handler = ObjectDrawRemoved;
			if (handler != null)
				handler(this, e);

		}

		public event EventHandler<PaintingExceptionEventArgs> PaintingException;

		private void InvokePaintingException(PaintingExceptionEventArgs e) {
			EventHandler<PaintingExceptionEventArgs> handler = PaintingException;
			if (handler != null) handler(this, e);
			else {
				AlertForm.Error.Show("Draw container on paint error", "Paint error", e.Error.ToString());
			}
		}

		Rectangle IDrawContainer.RectangleToScreen(Rectangle rectangle) {
			return
				RectangleToScreen(new Rectangle(rectangle.X - ScrollOffset.X, rectangle.Y - ScrollOffset.Y, rectangle.Width,
																				rectangle.Height));
		}

		Rectangle IDrawContainer.RectangleToClient(Rectangle rectangle) {
			var rec = RectangleToClient(rectangle);
			rec.Offset(-ScrollOffset.X, -ScrollOffset.Y);
			return rec;
		}

		public void Group(params ObjectDraw[] items) {
			GraphicsList.Group(items);
		}

		#region Implementation of IStreamSerialization

		public void ReadFrom(Stream stream) {
			stream.Read(out _lockDesign);
			GraphicsList.ReadFrom(stream);
		}

		public void WriteTo(Stream stream) {
			stream.Write(_lockDesign);
			GraphicsList.WriteTo(stream);
		}

		#endregion
	}
}
